<?php
/**
 * Session save handler
 *
 * @package CMS/Session/SaveHandler
 * @version 1.0
 */
class CMS_Session_SaveHandler_Doctrine implements Zend_Session_SaveHandler_Interface
{
    const ID_COLUMN         = 'idColumn';
    const MODIFIED_COLUMN   = 'modifiedColumn';
    const LIFETIME_COLUMN   = 'lifetimeColumn';
    const DATA_COLUMN       = 'dataColumn';

    const LIFETIME          = 'lifetime';
    const OVERRIDE_LIFETIME = 'overrideLifetime';

    /**
     * Table instance
     *
     * @var Doctrine_Table
     */
    protected $_table;

    /**
     * Session table session id column
     *
     * @var array
     */
    protected $_idColumn = 'session_id';

    /**
     * Session table last modification time column
     *
     * @var string
     */
    protected $_modifiedColumn = 'modified';

    /**
     * Session table lifetime column
     *
     * @var string
     */
    protected $_lifetimeColumn = 'lifetime';

    /**
     * Session table data column
     *
     * @var string
     */
    protected $_dataColumn = 'data';

    /**
     * Session lifetime
     *
     * @var int
     */
    protected $_lifetime = false;

    /**
     * Whether or not the lifetime of an existing session should be overridden
     *
     * @var boolean
     */
    protected $_overrideLifetime = false;

    /**
     * Contains cached records
     *
     * @see _getRecord()
     * @var array
     */
    protected $_recordCache = array();

    /**
     * Constructor
     *
     * $config is an instance of Zend_Config or an array of key/value pairs,
     * containing configuration options for Losc_Session_SaveHandler_Doctrine:
     *
     * name             => (string) Name of session record class
     * idColumn         => (string) [optional] Session table session id column.
     *                     Defaults to 'session_id'.
     * modifiedColumn   => (string) [optional] Session table last modification
     *                     time column. Defaults to 'modified'.
     * lifetimeColumn   => (string) [optional] Session table lifetime column.
     *                     Defaults to 'lifetime'.
     * dataColumn       => (string) [optional] Session table data column.
     *                     Defaults to 'data'.
     * lifetime         => (integer) [optional] Session lifetime. Defaults to
     *                     ini_get('session.gc_maxlifetime')
     * overrideLifetime => (boolean) [optional] Whether or not the lifetime of
     *                     an existing session should be overridden. Default
     *                     is false.
     *
     * @param  Zend_Config|array $config  user-provided configuration
     * @throws Zend_Session_SaveHandler_Exception
     */
    public function __construct($config)
    {
        if ($config instanceof Zend_Config)
        {
            $config = $config->toArray();
        } else if (!is_array($config))
        {
            $msg = '$config must be an instance of Zend_Config or array of '
                    . 'key/value pairs containing configuration options for '
                    . __CLASS__;
            throw new Zend_Session_SaveHandler_Exception($msg);
        }

        // set options
        foreach ($config as $key => $value)
        {
            switch ($key)
            {
                case self::ID_COLUMN:
                    $this->_idColumn = (string) $value;
                    break;
                case self::MODIFIED_COLUMN:
                    $this->_modifiedColumn = (string) $value;
                    break;
                case self::LIFETIME_COLUMN:
                    $this->_lifetimeColumn = (string) $value;
                    break;
                case self::DATA_COLUMN:
                    $this->_dataColumn = (string) $value;
                    break;
                case self::LIFETIME:
                    $this->setLifetime($value);
                    break;
                case self::OVERRIDE_LIFETIME:
                    $this->setOverrideLifetime($value);
                    break;
                default:
                    break;
            }
        }

        //$this->_checkRequiredColumns();
    }

    /**
     * Destructor
     *
     * @return void
     */
    public function __destruct()
    {
        //Zend_Session::writeClose();
    }

    /**
     * Sets session lifetime and optionally whether or not the lifetime of an
     * existing session should be overridden
     *
     * $lifetime === false resets lifetime to session.gc_maxlifetime
     *
     * @param int  $lifetime         new lifetime
     * @param bool $overrideLifetime [optional] whether the lifetime of an
     *                               existing session should be overridden
     * @return void
     * @throws Zend_Session_SaveHandler_Exception  if $lifetime is less than 0
     */
    public function setLifetime($lifetime, $overrideLifetime = null)
    {
        if ($lifetime < 0)
        {
            $msg = sprintf('$lifetime is less than 0 (%s)', $lifetime);
            throw new Zend_Session_SaveHandler_Exception($msg);
        } else if (empty($lifetime))
        {
            $this->_lifetime = (int) ini_get('session.gc_maxlifetime');
        } else
        {
            $this->_lifetime = (int) $lifetime;
        }

        if ($overrideLifetime != null)
        {
            $this->setOverrideLifetime($overrideLifetime);
        }

        return $this;
    }

    /**
     * Retrieves session lifetime
     *
     * @return int
     */
    public function getLifetime()
    {
        if (!$this->_lifetime)
        {
            $this->setLifetime(null);
        }

        return $this->_lifetime;
    }

    /**
     * Sets whether the lifetime of an existing session should be overridden
     *
     * @param  bool $overrideLifetime  override existing session lifetime
     * @return void
     */
    public function setOverrideLifetime($overrideLifetime)
    {
        $this->_overrideLifetime = (boolean) $overrideLifetime;
    }

    /**
     * Retrieve whether the lifetime of an existing session should be overridden
     *
     * @return bool  override existing session lifetime
     */
    public function getOverrideLifetime()
    {
        return $this->_overrideLifetime;
    }

    /**
     * Opens Session
     *
     * @param  string $save_path  ignored
     * @param  string $name       ignored
     * @return bool               always returns true
     */
    public function open($save_path, $name)
    {
        return true;
    }

    /**
     * Closes session
     *
     * @return boolean  always returns true
     */
    public function close()
    {
        return true;
    }

    /**
     * Reads session data for the given id
     *
     * @param  string $id  session id
     * @return string      serialized session data
     */
    public function read($id)
    {

        $session = Session::findOneById($id);

        if ($session)
        {
            if ($this->_getExpirationTime($session) > time())
            {
                // get data
                return $session->{$this->_dataColumn};
            } else
            {
                // delete session if expired
                $session->delete();
            }
        }

        return '';
    }

    /**
     * Writes session data for the given id
     *
     * @param  string $id    session id
     * @param  string $data  serialized data to write
     * @return bool          indicating success
     */
    public function write($id, $data)
    {
        $session = Session::findOneById($id);
        if ($session)
        {
            // session record exists: update lifetime
            $lifetime = $this->_getLifetime($session);
            if ($session->{$this->_lifetimeColumn} != $lifetime)
            {
                $session->{$this->_lifetimeColumn} =  $lifetime;
            }
        } else
        {
            // session record does not exist: create new
            $session                            = new Session();
            $session->{$this->_idColumn}        = $id;
            $session->{$this->_lifetimeColumn}  = $this->getLifetime();
        }

        // update modified and data
        $session->{$this->_modifiedColumn}  = time();
        $session->{$this->_dataColumn}      = $data;

        // try to save record
        return $session->save();
    }

    /**
     * Destroys session with the given id
     *
     * @param  string $id  session id
     * @return bool        indicating success
     */
    public function destroy($id)
    {
        $session = Session::findOneById($id);
        if ($session)
        {
            $session->delete();
            return true;
        }

        return false;
    }

    /**
     * Garbage Collection method
     *
     * @param  int  $maxlifetime  ignored
     * @return true
     */
    public function gc($maxlifetime)
    {
        return Session::clearSessions();
    }

    /**
     * Check for required session table columns
     *
     * @return void
     * @throws Zend_Session_SaveHandler_Exception
     */
    protected function _checkRequiredColumns()
    {
        $col = false;

        if ($this->_idColumn === null)
        {
            $key = self::ID_COLUMN;
            $col = 'id';
        } else if ($this->_modifiedColumn === null)
        {
            $key = self::MODIFIED_COLUMN;
            $col = 'session_modified';
        } else if ($this->_lifetimeColumn === null)
        {
            $key = self::LIFETIME_COLUMN;
            $col = 'lifetime';
        } else if ($this->_dataColumn === null)
        {
            $key = self::DATA_COLUMN;
            $col = 'session_data';
        }

        if ($col)
        {
            $msg = "Configuration must define '%s' which '
                 . 'names the session table %s column";
            $msg = sprintf($msg, $key, $col);
            throw new Zend_Session_SaveHandler_Exception($msg);
        }
    }

    /**
     * Retrieves session lifetime for the given record
     *
     * @param  Doctrine_Record $record  record to retrieve lifetime from
     * @return int                      record lifetime
     */
    protected function _getLifetime(Session $session)
    {
        if (!$this->_overrideLifetime)
            return (int) $session->{$this->_lifetimeColumn};

        return $this->getLifetime();
    }

    /**
     * Retrieves session expiration time for the given record
     *
     * @param Doctrine_Record $record  doctrine record
     * @return int
     */
    protected function _getExpirationTime(Session $session)
    {
        return (int) $session->{$this->_modifiedColumn} +
                $this->_getLifetime($session);
    }
}